package com.longlong.orm;

import com.longlong.pool.ConnectionPool;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author longlong
 */
public class SqlSession {

    private final ConnectionPool pool = ConnectionPool.getInstance();

    // 将 JDBC 操作封装成 方法

    //======  方案一 SpringJDBC start==========
    //  该方案有两个不好的地方
    //  1.传递objs数组里面的值有顺序
    //  2.objs参数可读性不强

    /**
     * 更新
     *
     * @param sql    sql 语句 : update student set name=?,age=?,class_id=? where id=?
     * @param params 参数
     * @throws Exception
     */
    public void update(String sql, Object... params) {
        Connection conn;
        PreparedStatement pstate;
        try {
            // 从 连接池中获取 连接
            conn = pool.getConnection();
            // 预编译 sql
            pstate = conn.prepareStatement(sql);
            // 设置参数
            for (int i = 0; i < params.length; i++) {
                pstate.setObject(i + 1, params[i]);
            }
            // 执行 sql
            pstate.executeUpdate();
            // 关闭
            pstate.close();
            conn.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void insert(String sql, Object... params) {
        update(sql, params);
    }

    public void delete(String sql, Object... params) {
        update(sql, params);
    }

    /**
     * 查询单条
     *
     * @param sql  sql 语句 : select * from student where sid = ? and sname = ?
     * @param rm   结果集 组装成 对象 的策略
     * @param objs SQL语句上的值
     * @param <T>  泛型
     * @return Object
     */
    public <T> T selectOne(String sql, RowMapper rm, Object... objs) {
        return (T) this.selectList(sql, rm, objs).get(0);
    }

    /**
     * 查询多条
     *
     * @param sql  sql 语句 : select * from student where sid = ? and sname = ?
     * @param rm   结果集 组装成 对象 的策略
     * @param objs SQL语句上的值
     * @param <T>  泛型
     * @return list集合
     */
    public <T> List<T> selectList(String sql, RowMapper rm, Object... objs) {
        List<T> list = new ArrayList();
        try {
            // 获取连接
            Connection connection = pool.getConnection();
            // 创建状态参数
            PreparedStatement pstat = connection.prepareStatement(sql);
            //6.拼接 sql 和 问号信息
            for (int i = 0; i < objs.length; i++) {
                pstat.setObject(i + 1, objs[i]);
            }
            // 执行查询操作
            ResultSet rs = pstat.executeQuery();

            // 处理结果集 将结果集的数据取出来 写入domain对象
            while (rs.next()) {
                // 策略模式
                // 自定义规则 进行结果组装
                T obj = (T) rm.mapperRow(rs);
                list.add(obj);
            }
            // 关闭资源
            rs.close();
            pstat.close();
            connection.close();// 底层是一个释放
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return list;
    }

    //======  方案一 SpringJDBC end==========

    //======  方案二 MyBatis start==========

    private final Handler handler = new Handler();

    /**
     * 更新
     *
     * @param sql sql 语句 : update student set name=#{sname},age=#{sage},class_id=#{classId} where id=#{id}
     * @param obj domain对象
     */
    public void update(String sql, Object obj) {
        try {
            // 1. 解析SQL
            SqlAndKey sqlAndKey = handler.parseSql(sql);

            // 2. 获取连接
            Connection connection = pool.getConnection();
            // 3. 状态参数
            PreparedStatement pstat = connection.prepareStatement(sqlAndKey.getSql());

            //6.将SQL语句和那些问号值组装完整---反射
            if (obj != null) {
                handler.handleParameter(pstat, obj, sqlAndKey.getKeyList());
            }
            //7.组合完毕 可以执行了
            pstat.executeUpdate();
            //8.关闭
            pstat.close();
            connection.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void insert(String sql, Object obj) {
        update(sql, obj);
    }

    public void delete(String sql, Object obj) {
        update(sql, obj);
    }

    /**
     * 查询单条
     *
     * @param sql        sql 语句 : select * from student where sid = #{sid} and sname = #{sname}
     * @param obj        domain对象
     * @param resultType 返回值类型 基本类型 / Map / domain实体类
     * @param <T>        泛型
     * @return 返回值类型对应的对象
     */
    public <T> T selectOne(String sql, Object obj, Class resultType) {
        return (T) this.selectList(sql,obj, resultType).get(0);
    }

    /**
     * 查询单条
     *
     * @param sql        sql 语句 : select * from student where sid = #{sid} and sname = #{sname}
     * @param obj        domain对象
     * @param resultType 返回值类型 基本类型 / Map / domain实体类
     * @param <T>        泛型
     * @return list集合
     */
    public <T> List<T> selectList(String sql, Object obj, Class<?> resultType) {
        List<T> list = new ArrayList<>();
        try {
            //1.解析sql
            SqlAndKey sqlAndKey = handler.parseSql(sql);
            //2.获取连接
            Connection connection = pool.getConnection();
            //3.状态参数
            PreparedStatement pstat = connection.prepareStatement(sqlAndKey.getSql());
            //4.把sql和问号拼接在一起
            if (obj != null) {
                handler.handleParameter(pstat, obj, sqlAndKey.getKeyList());
            }
            //5.执行操作
            ResultSet rs = pstat.executeQuery();
            //6.处理结果(将结果集的值取出来 存入一个domain对象)
            while (rs.next()) {
                list.add((T) handler.handleResult(rs, resultType));
            }
            //7.关闭
            rs.close();
            pstat.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return list;
    }

    //======  方案二 MyBatis end==========

    /**
     * 获取Mapper代理对象
     *
     * @param clazz StudentDao.class
     * @return 代理对象
     * @param <T> 泛型
     */
    public <T> T getMapper(Class<?> clazz) {
        //  1.类加载器ClassLoader
        ClassLoader loader = clazz.getClassLoader();
        //  2.Class[] 加载的类  通常数组就一个元素
        Class<?>[] interfaces = new Class[]{clazz};
        //  3.InvocationHandler 接口 ==> 具体实现接口，告知具体该如何做事
        InvocationHandler h = new InvocationHandlerImpl();
        //  4.创建一个代理对象
        return (T)Proxy.newProxyInstance(loader,interfaces,h);
    }

}
